package htmlcli

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"reflect"
	"runtime"
	"strconv"

	lua "github.com/yuin/gopher-lua"
	"github.com/zserge/lorca"
	"htmlcli/conf"
	"htmlcli/json"
)

type App struct {
	conf   *conf.Config
	logger *log.Logger
	ui     lorca.UI
	svr    *FileServer
	signC  chan os.Signal
	lState *lua.LState
}

func (a *App) init() {
	fd, err := os.OpenFile(a.conf.App.LogPath+"app.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm|os.ModeTemporary)
	if err != nil {
		fd = os.Stdout
	}
	a.logger = log.New(fd, a.conf.App.Name, 0)
	//log.SetOutput(fd)
	args := make([]string, 0)
	if runtime.GOOS == "linux" {
		args = append(args, "--class=Lorca")
	}
	a.ui, err = lorca.New("", "", a.conf.App.Width, a.conf.App.Height, args...)
	if err != nil {
		a.logger.Fatal("init ui", err.Error())
	}
	a.svr = NewFileServer(a.conf.App.Public, a.conf.App.Port)

	a.lState = lua.NewState()
	a.lState.SetGlobal("CallJsFunc", a.lState.NewFunction(a.RunJsFunc))
	a.Bind()
}

func NewApp(conf *conf.Config) *App {
	a := &App{
		conf:  conf,
		signC: make(chan os.Signal),
	}
	a.init()
	return a
}

func (a *App) Run() error {
	err := a.ui.Load(fmt.Sprintf("%s:%d", a.conf.App.Addr, a.conf.App.Port))
	if err != nil {
		return err
	}
	defer func() { _ = a.ui.Close() }()
	signal.Notify(a.signC, os.Interrupt)
	select {
	case <-a.signC:
	case <-a.ui.Done():
	}
	close(a.signC)
	return nil
}

func (a *App) Bind() {
	_ = a.ui.Bind("RunLuaScript", a.RunLuaScript)
}

// RunJsFunc lua 运行 js 方法
func (a *App) RunJsFunc(L *lua.LState) int {
	funcName := L.ToString(1)
	params := L.ToTable(2)
	p, err := a.Table2JsonString(params)
	a.logger.Println("lua call js:f:", funcName, ",p:", params)

	if err != nil {
		L.Push(a.NewErrorTable(err))
		return 1
	}
	t := L.NewTable()
	rtn := a.ui.Eval(fmt.Sprintf("%s(%s)", funcName, p)).Object()
	for k, v := range rtn {
		t.RawSetString(k, lua.LString(v.String()))
	}
	L.Push(a.NewSuccessTable(t))
	return 1
}

// RunLuaScript js 运行 lua 方法
func (a *App) RunLuaScript(filename, fn string, params map[string]interface{}) string {
	a.logger.Println("js call lua:f", filename, ",fn:", fn, ",p:", params)
	// 载入文件
	err := a.lState.DoFile(a.conf.App.LuaPath + "/" + filename + ".lua")
	if err != nil {
		return a.NewErrorString(err)
	}
	t := a.Object2Table(params)
	// 发起调用
	if err := a.lState.CallByParam(lua.P{
		Fn:      a.lState.GetGlobal(fn),
		NRet:    1,
		Protect: true,
	}, a.NewSuccessTable(t)); err != nil {
		return a.NewErrorString(err)
	}
	// 处理返回值
	rtn := a.lState.ToTable(-1)
	a.lState.Pop(1)
	return a.NewSuccessString(a.Table2Map(rtn))
}

func (a *App) Object2Table(param map[string]interface{}) lua.LValue {
	t := a.lState.NewTable()
	for k, v := range param {
		switch kind := reflect.TypeOf(v).Kind(); kind {
		case reflect.String:
			t.RawSetString(k, lua.LString(v.(string)))
		case reflect.Bool:
			t.RawSetString(k, lua.LBool(v.(bool)))
		default:
			if kind >= reflect.Int && kind <= reflect.Uint64 {
				t.RawSetString(k, lua.LNumber(v.(int64)))
			} else if kind == reflect.Float64 || kind == reflect.Uint32 {
				t.RawSetString(k, lua.LNumber(v.(float64)))
			} else {
				t.RawSetString(k, lua.LString(fmt.Sprintf("%v", v)))
			}
		}
	}
	return t
}

func (a *App) Table2JsonString(t lua.LValue) (string, error) {
	p := a.Table2Map(t)
	return json.EncodeString(p)
}

func (a *App) Table2Map(t lua.LValue) map[string]interface{} {
	table, ok := t.(*lua.LTable)
	if !ok {
		return nil
	}
	p := make(map[string]interface{})
	table.ForEach(func(k lua.LValue, v lua.LValue) {
		switch v.Type() {
		case lua.LTString:
			p[k.String()] = v.String()
		case lua.LTNumber:
			val, err := strconv.ParseFloat(v.String(), 10)
			if err == nil {
				p[k.String()] = val
			} else {
				p[k.String()] = 0
			}
		case lua.LTBool:
			p[k.String()] = v.String() == "true"
		default:
			p[k.String()] = v.String()
		}
	})
	return p
}

func (a *App) NewErrorTable(err error) lua.LValue {
	t := a.lState.NewTable()
	t.RawSetString("Code", lua.LNumber(0))
	t.RawSetString("Msg", lua.LString(err.Error()))
	return t
}

func (a *App) NewSuccessTable(v lua.LValue) lua.LValue {
	t := a.lState.NewTable()
	t.RawSetString("Code", lua.LNumber(1))
	t.RawSetString("Msg", lua.LString("Success"))
	t.RawSetString("Payload", v)
	return t
}

func (a *App) NewErrorString(err error) string {

	rtn := map[string]interface{}{}
	rtn["Code"] = 0
	rtn["Msg"] = err.Error()
	s, _ := json.EncodeString(rtn)
	return s
}

func (a *App) NewSuccessString(data interface{}) string {
	rtn := map[string]interface{}{}
	rtn["Code"] = 1
	rtn["Msg"] = "OK"
	rtn["Data"] = data
	s, _ := json.EncodeString(rtn)
	return s
}

func GetType(i interface{}) reflect.Kind {
	t := reflect.TypeOf(i)
	return t.Kind()
}
